import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Observer;

import javax.swing.JPanel;

/**
 * Responsible for displaying the correct cell values inside a 9x9 grid. 
 * Is also responsible for updating a cell when an action is performed such as on mouse over. 
 * Additionally can set or retrieve information from a selected cell.
 */
public class GridControl extends JPanel implements MouseListener, MouseMotionListener, GridInterface
{
	private Cell[][] cells;
	private Color backgroundColor;
	private Color hoverColor;
	private Color numSelectBackgroundColor;
	private Color highlightColor;
	private Color errorColor;
	private Color lockColor;
	private Font gridControlFont;
	private Font numSelectFont;
	private int xGridOffset;
	private int yGridOffset;
	private int gridWidth;
	private int gridHeight;
	private int cellWidth;
	private int cellHeight;
	private int numSelectCellWidth;
	private int numSelectCellHeight;
	private int numSelectSuperCellWidth;
	private int numSelectSuperCellHeight;
	private int superCellWidth = SudokuFrontEnd.noCellColumns * cellWidth;
	private int superCellHeight = SudokuFrontEnd.noCellRows * cellHeight;
	private AffineTransform affineTransform;

	private Point mouseMove;
	private Point mouseClick;
	private Point prevMouseClick;
	private Point mousePress;
	private Point mouseRelease;
	private boolean numSelectActivated;
	private Point numSelectCellSelected;
	int numSelectControlData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
	
	private newObservable numberEnteredEvent = new newObservable();
	
	/**
	 * Initialises the sudoku grid based on the following default settings
	 */
	public GridControl()
	{
		initialiseCells();
		backgroundColor = Color.white;
		hoverColor = Color.yellow;
		numSelectBackgroundColor = Color.orange;
		lockColor = Color.gray;
		highlightColor = Color.green;
		errorColor = Color.red;
		affineTransform = new AffineTransform();
		prevMouseClick = new Point();
		mouseClick = new Point();
		mouseMove = new Point();
		numSelectCellSelected = new Point();
		numSelectActivated = false;
		this.setBackground(backgroundColor);
		mousePress = new Point();
		mouseRelease = new Point();
		
        addMouseListener(this);
        addMouseMotionListener(this);
		
        cellWidth = Math.min((getWidth()) / (SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns + 2), getHeight() / (SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows + 2));
        cellHeight = cellWidth;
        gridWidth = cellWidth * SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns;
        gridHeight = cellHeight * SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows;
    	superCellWidth = 3 * cellWidth;
    	superCellHeight = 3 * cellHeight;
    	numSelectCellWidth = cellWidth * 2 / 3;
    	numSelectCellHeight = cellHeight * 2 / 3;
    	numSelectSuperCellWidth = 3 * numSelectCellWidth;
    	numSelectSuperCellHeight = 3 * numSelectCellHeight;
		affineTransform.setToIdentity();
		xGridOffset = (getWidth() - (cellWidth * (SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns))) / 2;
		yGridOffset = (getHeight() - (cellHeight * (SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows))) / 2;
        affineTransform.translate(xGridOffset, yGridOffset);
	}
	

	/* 
	 * Used to restart on a new grid
	 */
	public void resetControl()
	{
		initialiseCells();
		repaint();
		numSelectActivated = false;
	}
	
	/**
	 * Initialises a 9x9 "empty" grid filled with 0
	 */
	public void initialiseCells()
	{
		cells = new Cell[SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows][SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows];
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				cells[i][j] = new Cell(false, false, false, false, 0);
			}
		}
	}
	
	/* 
	 * Changes the highlighted state of a cell then repaints the control
	 */
	public void setCellHighlighted(boolean cellData, Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		cells[p.x][p.y].setHighlighted(cellData);
		this.updateUI();
	}
	
	/* 
	 * Changes highlighted status of all cells and repaints the control
	 */
	public void setCellsHighlighted(boolean[][] cellData)
	{
		Point p;
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
				cells[p.x][p.y].setHighlighted(cellData[i][j]);
			}
		}
		this.updateUI();
	}

	/* 
	 * Changes the ability of a cell to be modified by the user and repaints hte control
	 */
	public void setCellLocked(boolean cellData, Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		cells[p.x][p.y].setLocked(cellData);
		this.updateUI();
	}
	
	/* 
	 * Locks all cells to prevent user modification at end game and repaints the control
	 */
	public void setCellsLocked(boolean[][] cellData)
	{
		Point p;
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
				cells[p.x][p.y].setLocked(cellData[i][j]);
			}
		}
		this.updateUI();
	}

	/* 
	 * Changes the cell status based on legality of number assigned and repaints the control
	 */
	public void setCellError(boolean cellData, Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		cells[p.x][p.y].setError(cellData);
		this.updateUI();
	}
	
	/* 
	 * Updates all cells as legal or illegal and repaints the control
	 */
	public void setCellsError(boolean[][] cellData)
	{
		Point p;
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
				cells[p.x][p.y].setError(cellData[i][j]);
			}
		}
		this.updateUI();
	}
	
	
	/* 
	 * Updates cell display based on a 2D array
	 */
	public void setCellsData(int[][] cellData)
	{
		Point p;
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
				cells[p.x][p.y].setData(cellData[i][j]);
				numberEnteredEvent.setChanged();
		    	numberEnteredEvent.notifyObservers(new Point(i, j));
			}
		}
		this.updateUI();
	}
	/* 
	 * Retrieves the data from cells and stores in a 2D array to be returned
	 */
	public int[][] getCellsData()
	{
		Point p;
		int[][] returnData = new int[SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows][SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows];
		for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
			{
				p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
				returnData[i][j] = cells[p.x][p.y].getData();
			}
		}
		return returnData;
	}
	
	/* 
	 * Change data content of one particular cell
	 */
	public void setCellData(int cellData, Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		cells[p.x][p.y].setData(cellData);
		numberEnteredEvent.setChanged();
    	numberEnteredEvent.notifyObservers(new Point(xy.x, xy.y));
		this.updateUI();
	}
	
	/* 
	 * Retrieve data content of one particular cell
	 */
	public int getCellData(Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		return cells[p.x][p.y].getData();
	}
	
	/* 
	 * Retrieve the cell corresponding to mouse coordinates
	 */
	public Cell getCell(Point xy)
	{
		Point p = SudokuLibrary.convertFromXYToSC(xy);
		return cells[p.x][p.y];
	}
	
	/* 
	 * Returns a 2D array corresponding to cell contents
	 */
	public Cell[][] getCells()
	{
		return cells;
	}

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
    	paintState((Graphics2D)g);
    }
    
    /**
     * Used to draw basic cell structure
     * @param g Grid of cells
     */
    public void paintState(Graphics2D g)
    {
    	g.transform(affineTransform);
    	drawBase(g);
    	drawCells(g);
    	drawCellData(g);
    	drawOuterGrid(g);
    	drawNumSelectControl(g);
    }
    /**
     * Draws the based for the grids
     * @param g Grid of cells
     */
    public void drawBase(Graphics2D g)
    {
    	Color prevColor = g.getColor();
		g.setColor(backgroundColor);
    	g.fillRect(0, 0, getWidth(), getHeight());
		g.setColor(prevColor);
    }
    
    /**
     * Draws an accentuated border of the super cell structure
     * @param g Grid of cells
     */
    public void drawOuterGrid(Graphics2D g)
    {
        g.setStroke(new BasicStroke(3));
        for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns; i++)
        {
        	for (int j = 0; j < SudokuFrontEnd.noSuperCellRows; j++)
            {
	    		int xOffset = i * superCellWidth;
	    		int yOffset = j * superCellHeight;
	    		
	    		g.drawRect(xOffset, yOffset, superCellWidth, superCellHeight);
            }
        }
        g.setStroke(new BasicStroke(1));
    }
    
    /**
     * Colours a cell based on status and mouse interaction.
     * A cell will change on mouse over, legality of entered number, end game status.
     * Colouring will also be darker if the cell is locked and unavailable for user change
     * @param g Grid of cells
     */
    public void drawCells(Graphics2D g)
    {
    	 for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
         {
         	for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
             {
 	    		int xOffset = i * cellWidth;
 	    		int yOffset = j * cellHeight;
 	    		Point p = SudokuLibrary.convertFromXYToSC(new Point(i, j));

	    		Color prevColor = g.getColor();
	    		Color rectColor = Color.white;
	    		//Colours a cell on mouse over
 	    		if (cells[p.x][p.y].isHover())
 	    		{
 	    			rectColor = mixColors(rectColor, hoverColor);
 	    			g.setColor(rectColor);
 	    			g.fillRect(xOffset, yOffset, cellWidth, cellHeight);
 	    		}
 	    		//Colours a cell when highlighted at end game
 	    		if (cells[p.x][p.y].isHighlighted())
 	    		{
 	    			rectColor = mixColors(rectColor, highlightColor);
	    			g.setColor(rectColor);
	    			g.fillRect(xOffset, yOffset, cellWidth, cellHeight);
 	    		}
 	    		//Colours a cell based on legality, i.e. if a number is "wrong" for that particular solution
 	    		if (cells[p.x][p.y].isError())
 	    		{
 	    			rectColor = mixColors(rectColor, errorColor);
 	    			g.setColor(rectColor);
 	    			g.fillRect(xOffset, yOffset, cellWidth, cellHeight);
 	    		}
 	    		//Colours a cell different when it is locked and unavailable for user change
 	    		if (cells[p.x][p.y].isLocked())
 	    		{
 	    			rectColor = mixColors(rectColor, lockColor);
	    			g.setColor(rectColor);
	    			g.fillRect(xOffset, yOffset, cellWidth, cellHeight);
 	    		}
    			g.setColor(prevColor);
    			
 	    		g.drawRect(xOffset, yOffset, cellWidth, cellHeight);
             }
         }
    }
    
    /**
     * Used to generate a new colour when two colour states stack, denotes multiple states. Biases the colour towards the second colour
     * @param c1 First colour
     * @param c2 Second colour
     * @return Hybrid of the two colours
     */
    public Color mixColors(Color c1, Color c2)
    {
    	return new Color((int)((c1.getRed() + c2.getRed() * 2) / 3), (int)((c1.getGreen() + c2.getGreen() * 2) / 3), (int)((c1.getBlue() + c2.getBlue() * 2) / 3));
    }
    
    /**
     * Fills cells based on their data content.
     * A cell containing 0 is regarded as empty and content not displayed
     * @param g Grid of cells
     */
    public void drawCellData(Graphics2D g)
    {
        g.setFont(g.getFont().deriveFont((float)cellWidth / 2));
    	for (int i = 0; i < SudokuFrontEnd.noSuperCellColumns * SudokuFrontEnd.noSuperCellRows; i++)
        {
        	for (int j = 0; j < SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noCellRows; j++)
            {
 	    		int xOffset = i * cellWidth;
 	    		int yOffset = j * cellHeight;
 	    		Point p = SudokuLibrary.convertFromXYToSC(new Point(i, j));
 	    		if (!(cells[p.x][p.y].getData() == 0))
 	    		{
 		    		drawNumber(g, xOffset, yOffset, String.valueOf(cells[p.x][p.y].getData()), cellWidth, cellHeight, gridControlFont);
 	    		}
            }
        }
    }
    
    /**
     * Draws a number given mouse coordinates on the screen. 
     * Used to allow dynamic user selection of numbers for cell input
     * @param g Grid of cells
     * @param xOffset x coordinate
     * @param yOffset y coordinate
     * @param cellData Content of cell
     * @param cellWidth Width of cell
     * @param cellHeight Height of cell
     * @param font Font for display purposes
     */
    private void drawNumber(Graphics2D g, int xOffset, int yOffset, String cellData, int cellWidth, int cellHeight, Font font)
    {
    	Font prevFont = g.getFont();
    	if (font != null)
    	{
	    	g.setFont(font);
	        Rectangle2D stringBoundsWidth = font.getStringBounds(cellData, g.getFontRenderContext());
	        LineMetrics stringBoundsHeight = font.getLineMetrics(cellData, g.getFontRenderContext());
	        
	        g.drawString(cellData, (xOffset + cellWidth / 2) - Math.round(stringBoundsWidth.getWidth() / 2),
	                        yOffset + (cellHeight / 2) + Math.round(2 * stringBoundsHeight.getAscent() / 5));
	    	g.setFont(prevFont);
    	}
    }
    
    /**
     * Controls where to bring up the 2D array of numbers for user input selection
     * @param g Grid of cells
     */
    public void drawNumSelectControl(Graphics2D g)
    {
    	if (numSelectActivated)
    	{
        	// repaint the control if it is activated
    		// position to paint it is numSelectCellSelected
    		Point cellXY = numSelectCellSelected;
    		int xCell = cellXY.x * cellWidth;
    		int yCell = cellXY.y * cellHeight;
    		int xControl = xCell + (cellWidth / 2) - (int)(numSelectCellWidth * 1.5);
    		int yControl = yCell + (cellHeight / 2) - (int)(numSelectCellHeight * 1.5);
    		int controlWidth = numSelectCellWidth * 3;
    		int controlHeight = numSelectCellHeight * 3;
    		
            g.setStroke(new BasicStroke(3));
            
            Color prevColor = g.getColor();
            g.setColor(numSelectBackgroundColor);
            g.fillRect(xControl, yControl, controlWidth, controlHeight);
            g.setColor(prevColor);
            g.drawRect(xControl, yControl, controlWidth, controlHeight);
            
            g.setStroke(new BasicStroke(1));
            
            drawNumSelectControlData(g, numSelectCellSelected);
    	}
    }
    
    /**
     * Displays the 9x9 array of numbers a user can select from to enter into the selected cell
     * @param g Grid of cells
     * @param cellSelected Current cell selected by user
     */
    private void drawNumSelectControlData(Graphics2D g, Point cellSelected)
    {
    	int xCell = cellSelected.x * cellWidth;
		int yCell = cellSelected.y * cellWidth;
		int xControl = xCell + (cellWidth / 2) - (int)(numSelectCellWidth * 1.5);
		int yControl = yCell + (cellHeight / 2) - (int)(numSelectCellHeight * 1.5);
		
		for (int i = 0; i < SudokuFrontEnd.noCellRows; i++)
		{
			for (int j = 0; j < SudokuFrontEnd.noCellColumns; j++)
			{
				g.drawRect(xControl + j * numSelectCellWidth, yControl + i * numSelectCellHeight, numSelectCellWidth, numSelectCellHeight);
				drawNumber(g, xControl + j * numSelectCellWidth, yControl + i * numSelectCellHeight, String.valueOf(numSelectControlData[(i * 3) + j]), numSelectCellWidth, numSelectCellHeight, numSelectFont);
			}
		}
    }
    
    public Point translate(Point p)
    {
    	return new Point(p.x - (int)affineTransform.getTranslateX(), p.y - (int)affineTransform.getTranslateY());
    }
    
    public void addNumberEnteredObserver(Observer o)
    {
    	numberEnteredEvent.addObserver(o);
    }
    
    public void deleteNumberEnteredObserver(Observer o)
    {
    	numberEnteredEvent.deleteObserver(o);
    }    
	
	@Override
	public void mouseDragged(MouseEvent e)
	{
	}

	/* 
	 * Updates cells when a mouse has moved over, or left the cell
	 */
	@Override
	public void mouseMoved(MouseEvent e)
	{
		Point currCell = new Point(translate(e.getPoint()).x / cellWidth, translate(e.getPoint()).y / cellHeight);
		Point convCell = SudokuLibrary.convertFromXYToSC(currCell);
		Point prevCell = new Point(translate(mouseMove).x / cellWidth, translate(mouseMove).y / cellHeight);
		Point convPrevCell = SudokuLibrary.convertFromXYToSC(prevCell);
		if (!numSelectActivated)
		{
			if (SudokuLibrary.isPointInRect(e.getPoint(), new Rectangle(xGridOffset, yGridOffset, gridWidth, gridHeight)) &&
					SudokuLibrary.isPointInRect(mouseMove, new Rectangle(xGridOffset, yGridOffset, gridWidth, gridHeight)))
			{
				if (!mouseMove.equals(e.getPoint()))
				{
					cells[convPrevCell.x][convPrevCell.y].setHover(false);
				}
				cells[convCell.x][convCell.y].setHover(true);
			}
			else if (SudokuLibrary.isPointInRect(mouseMove, new Rectangle(xGridOffset, yGridOffset, gridWidth, gridHeight)))
			{
				cells[convPrevCell.x][convPrevCell.y].setHover(false);
			}
			mouseMove.setLocation(e.getPoint());
	        this.updateUI();
		}
	}
	
	/**
	 * Calls a series of methods associated when a mouse is clicked.
	 * This includes whether the click location is valid.
	 * Updates a cell depending on selected value if it applies, or clears
	 * @param thePoint Mouse click location
	 */
	public void performMouseClick(Point thePoint)
	{
		prevMouseClick.setLocation(mouseClick.getLocation());
		mouseClick.setLocation(thePoint);
		
		Point prevCell = new Point();
		prevCell.setLocation(Math.floor((float)translate(prevMouseClick).x / cellWidth), Math.floor((float)translate(prevMouseClick).y / cellHeight));
		Point currCell = new Point();
		currCell.setLocation(Math.floor((float)translate(mouseClick).x / cellWidth), Math.floor((float)translate(mouseClick).y / cellHeight));
		Point currNumSelectCell = new Point();
		currNumSelectCell.setLocation(Math.floor(((float)translate(mouseClick).x + (cellWidth / 2) - (int)(numSelectCellWidth * 1.5)) / numSelectCellWidth), Math.floor(((float)translate(mouseClick).y + (cellHeight / 2) - (int)(numSelectCellHeight * 1.5)) / numSelectCellHeight));
		Point convPrevCell = SudokuLibrary.convertFromXYToSC(prevCell);
		Point convCurrCell = SudokuLibrary.convertFromXYToSC(currCell);
		
		Point numSelectOffset = new Point();
		numSelectOffset.setLocation(((prevCell.getX() + 0.5) * cellWidth - (float)(numSelectSuperCellWidth * 0.5)), ((prevCell.getY() + 0.5) * cellHeight - (float)(numSelectSuperCellHeight * 0.5)));
		
		
		if (!numSelectActivated &&
				SudokuLibrary.isPointInRect(mouseClick, new Rectangle(xGridOffset, yGridOffset, gridWidth, gridHeight)))
		{
			// if not activated, activate
			if (!cells[convCurrCell.x][convCurrCell.y].isLocked())
			{
				numSelectCellSelected.setLocation(translate(mouseClick).x / cellWidth, translate(mouseClick).y / cellHeight);
				numSelectActivated = true;
			}
	        this.updateUI();
		}
		else if (numSelectActivated &&
				SudokuLibrary.isPointInRect(prevMouseClick, new Rectangle(xGridOffset, yGridOffset, gridWidth, gridHeight)) &&
				SudokuLibrary.isPointInRect(translate(mouseClick), new Rectangle(numSelectOffset.x, numSelectOffset.y, numSelectSuperCellWidth, numSelectSuperCellHeight)))
		{
			// if activated and cursor is around where the old cursor was clicked, change value of cell
			Point numSelectCell = new Point((translate(mouseClick).x - numSelectOffset.x) / numSelectCellWidth, (translate(mouseClick).y - numSelectOffset.y) / numSelectCellHeight);
			cells[convPrevCell.x][convPrevCell.y].setData(numSelectControlData[(numSelectCell.y * SudokuFrontEnd.noCellColumns) + numSelectCell.x]);
        	numSelectActivated = false;
        	numberEnteredEvent.setChanged();
        	numberEnteredEvent.notifyObservers(prevCell);
            this.updateUI();
		}
		else if (numSelectActivated &&
				!SudokuLibrary.isPointInRect(mouseClick, new Rectangle(translate(numSelectOffset).x, translate(numSelectOffset).y, numSelectSuperCellWidth, numSelectSuperCellHeight)))
		{
			// if activated and cursor is not near old click position, remove overlay
			if (SudokuLibrary.isPointInRect(prevCell, new Rectangle(0, 0, 9, 9)))
			{
				cells[convPrevCell.x][convPrevCell.y].setData(0);
	        	numberEnteredEvent.setChanged();
	        	numberEnteredEvent.notifyObservers(prevCell);
			}
			numSelectActivated = false;
	        this.updateUI();
		}
	}

	@Override
	public void mouseClicked(MouseEvent e)
	{
		if (!e.getPoint().equals(mouseRelease))
		{
			performMouseClick(e.getPoint());
		}
	}

	@Override
	public void mouseEntered(MouseEvent e)
	{	
	}

	@Override
	public void mouseExited(MouseEvent e)
	{
	}

	@Override
	public void mousePressed(MouseEvent e)
	{
		mousePress.setLocation(e.getPoint());
	}

	@Override
	public void mouseReleased(MouseEvent e)
	{
		mouseRelease.setLocation(e.getPoint());
		if (Math.sqrt(Math.pow(mousePress.x - mouseRelease.x, 2) + Math.pow(mousePress.y - mousePress.y, 2)) <= Math.min(numSelectSuperCellWidth, numSelectCellHeight))
		{
			performMouseClick(mouseRelease);
		}
	}
	
	/* 
	 * When resizing the play area of the grid, ensures all elements are in proportion
	 */
	public void componentResized()
	{
		Graphics g2 = this.getGraphics();
		Graphics2D g = (Graphics2D)g2;
        cellWidth = Math.min((getWidth()) / (SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns + 3), getHeight() / (SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows + 3));
        cellHeight = cellWidth;
        gridWidth = cellWidth * SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns;
        gridHeight = cellHeight * SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows;
    	superCellWidth = 3 * cellWidth;
    	superCellHeight = 3 * cellHeight;
    	numSelectCellWidth = cellWidth * 12 / 10;
    	numSelectCellHeight = cellHeight * 12 / 10;
    	numSelectSuperCellWidth = 3 * numSelectCellWidth;
    	numSelectSuperCellHeight = 3 * numSelectCellHeight;
		gridControlFont = g.getFont().deriveFont((float)cellWidth / 2);
    	numSelectFont = g.getFont().deriveFont((float)numSelectCellWidth / 2);
		affineTransform.setToIdentity();
		xGridOffset = (getWidth() - (cellWidth * (SudokuFrontEnd.noCellColumns * SudokuFrontEnd.noSuperCellColumns))) / 2;
		yGridOffset = (getHeight() - (cellHeight * (SudokuFrontEnd.noCellRows * SudokuFrontEnd.noSuperCellRows))) / 2;
        affineTransform.translate(xGridOffset, yGridOffset);
        g.setFont(g.getFont().deriveFont((float)cellWidth / 2));
        this.updateUI();
	}
	
	public void componentShown(Graphics g)
	{
	}

	/* 
	 * Determines the scaling of the Grid to be presented on a computer screen
	 */
	@Override
	public void grabImage(Graphics g, Point xy, Point dim) 
	{
		AffineTransform clone = (AffineTransform)affineTransform.clone();
		affineTransform.setToIdentity();
		// An A4 page is 8.5 inches across and 10.3 inches tall
		// The magic number to get a sudoku grid to fit onto the page is to have a pixel scaling of
		// I want the sudoku puzzle to be ~ 6 inches compared to A4 paper
		// sudoku puzzle is 450 pixels wide
		// each pixel must be 6 / 450 inches wide
		// the puzzle width is 6 * 72 = 432 points wide
		
		double smallestDimension = Math.min(dim.x, dim.y);
		double scaling = (smallestDimension / (((double)cellWidth * 9) * 4/3)) * (50 / (double)cellWidth);
		affineTransform.translate(xy.x + (dim.getX() / 2) - (((double)cellWidth * 9) * scaling) / 2, xy.y + (dim.getY() / 2) - (((double)cellWidth * 9) * scaling) / 2);
		affineTransform.scale(scaling, scaling);
		this.paint(g);
		affineTransform.setToIdentity();
		((Graphics2D)g).setTransform(affineTransform);
		affineTransform = clone;
	}
	
}
